namespace Stee.Cap8.Service
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using Grpc.Core;
    using NLog;

    public static class GrpcServiceContext
    {
        private const string REQUEST_ID = "x-requestid";
        private const string SESSION_ID = "x-sessionid";
        private static readonly IDictionary<Type, bool> GRPCServiceReadyness= new Dictionary<Type, bool>();
        private static readonly IDictionary<Type, bool> GRPCServiceDegraded =new Dictionary<Type, bool>();
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();


        #region Health check

        /// <summary>
        /// All gRPC service need to register before initialization.
        /// By default: a new registered service is not ready to accept request. 
        ///   And it is not in degraded mode.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public static void RegisterService<T>()
        {
            var serviceType = typeof(T);
            if(Logger.IsDebugEnabled)
            {
                Logger.Debug($"{nameof(RegisterService)} start. Service Type: {serviceType.FullName}");
            }
            if(GRPCServiceReadyness.ContainsKey(serviceType))
            {
                Logger.Error($"Service Type '{serviceType.FullName}' is already registered.");
                return;
            }
            GRPCServiceReadyness.Add(serviceType, false); 
            GRPCServiceDegraded.Add(serviceType, false);
        }
        /// <summary>
        /// Set service readiness.
        /// gRPC service required to call this function after initialized 
        /// and ready to accept request.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ready">
        ///  true: ready. false: not ready
        /// </param>
        public static void SetServiceReadiness<T>(bool ready)
        {
            var serviceType = typeof(T);
            if (Logger.IsDebugEnabled)
            {
                Logger.Debug($"{nameof(SetServiceReadiness)} start. Service Type: {serviceType.FullName}, ready: {ready}");
            }
            if (GRPCServiceReadyness.ContainsKey(serviceType))
            {
                GRPCServiceReadyness[serviceType] = ready;
            }
            else
            {
                Logger.Error($"Service Type '{serviceType.FullName}' is not registered.");
                
            }
        }
        /// <summary>
        /// Set service in degraded mode.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="degraded"></param>
        public static void SetServiceInDegradeModel<T>(bool degraded)
        {
            var serviceType = typeof(T);
            if (Logger.IsDebugEnabled)
            {
                Logger.Debug($"{nameof(SetServiceInDegradeModel)} start. Service Type: {serviceType.FullName}, DegradedMode: {degraded}");
            }
            if (GRPCServiceDegraded.ContainsKey(serviceType))
            {
                GRPCServiceDegraded[serviceType] = degraded;
            }
            else
            {
                Logger.Error($"Service Type '{serviceType.FullName}' is not registered.");
                
            }
        }


        internal static bool IsReady()
        {
            if(GRPCServiceReadyness.Any()) 
            {
                if(GRPCServiceReadyness.Any(t=> !t.Value ))
                {
                    if (Logger.IsInfoEnabled)
                    {
                        var notReadyServices = string.Join(',', GRPCServiceReadyness.Where(t => !t.Value)
                            .Select(t => t.Key.FullName));
                        Logger.Info($"This services are not ready: {notReadyServices}");
                    }
                    return false;
                }
                return true;
            }
            else // no registered service
            {
                return false;
            }
        }

        internal static bool IsDegraded()
        {
            if (GRPCServiceDegraded.Any(t=> t.Value))
            {
                if (Logger.IsInfoEnabled)
                {
                    var degradedServices = string.Join(',', GRPCServiceDegraded.Where(t => t.Value)
                        .Select(t => t.Key.FullName));
                    Logger.Info($"This services are in degraded mode: {degradedServices}");
                }

                return true;
            }
            else // no registered service
            {
                return false;
            }
        }
        #endregion Health check

        /// <summary>
        /// Get Request ID from gRPC header.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static string GetRequestID(ServerCallContext context)
        {
            if (context?.UserState !=null && context.UserState.ContainsKey(REQUEST_ID))
            {
                return context.UserState[REQUEST_ID].ToString();
            }
            
            else
            {
                var metadataEntry = context?.RequestHeaders?.FirstOrDefault(m =>
            string.Equals(m.Key, REQUEST_ID, StringComparison.Ordinal));
                if (metadataEntry?.Value == null || Equals(metadataEntry, default(Metadata.Entry)))
                {
                    if (Logger.IsWarnEnabled)
                    {
                        Logger.Warn("Request ID is null.");
                    }

                    return null;
                }
                context.UserState[REQUEST_ID] = metadataEntry.Value;
                return metadataEntry.Value;
            }
        }
        /// <summary>
        /// Get user information from Context.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static ClaimsPrincipal GetUser(ServerCallContext context)
        {
            if (Logger.IsWarnEnabled && context.GetHttpContext().User == null)
            {
                Logger.Warn("User does not exist in gRPC Context.");
            }
            return context.GetHttpContext()?.User;
        }
        /// <summary>
        /// Get Session ID from gRPC Header.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static string GetSessionID(ServerCallContext context)
        {
            
            if (context?.UserState != null && context.UserState.ContainsKey(SESSION_ID))
            {
                return context.UserState[SESSION_ID].ToString();
            }
            else
            {
                var metadataEntry = context?.RequestHeaders?.FirstOrDefault(m =>
            string.Equals(m.Key, SESSION_ID, StringComparison.Ordinal));
                if (metadataEntry?.Value == null || Equals(metadataEntry, default(Metadata.Entry)))
                {
                    if (Logger.IsWarnEnabled)
                    {
                        Logger.Warn("Session ID does not exist in gRPC Context.");
                    }

                    return null;
                }
                context.UserState[SESSION_ID] = metadataEntry.Value;
                return metadataEntry.Value;
            }
        }



    }
}


